package com.zzk.cloudfile.demos.web;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zzk.cloudfile.demos.web.common.AjaxResult;
import com.zzk.cloudfile.demos.web.common.SliceBadException;
import com.zzk.cloudfile.demos.web.util.ExecutorUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.stream.Collectors;


@Controller
public class BasicController {

    private final String identification = "-slice-";
    private final String uploadslicedir = "uploads" + File.separator + "slice" + File.separator;//分片目录
    private final String uploaddir = "uploads" + File.separator + "real" + File.separator;//实际文件目录
//    private final String physicalPath = "D:\\cloudfile\\";
    private final String physicalPath = "/cloudfile/";
    private final String virtualPath = "http://10.123.123.125:8081/uploads/real/";


    // http://127.0.0.1:8080/hello?name=lisi
    @RequestMapping("/hello")
    @ResponseBody
    public String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) {
        return "Hello " + name;
    }


    //获取分片
    @GetMapping("/testing/{fileName}/{fileSliceSize}/{fileSize}")
    @ResponseBody
    public AjaxResult testing(@PathVariable String fileName, @PathVariable long fileSliceSize, @PathVariable long fileSize) {
        String dir = fileNameMd5Dir(fileName, fileSize);
        String absolutePath = physicalPath + uploadslicedir + dir;
        File file = FileUtil.mkdir(absolutePath);
        if (!file.exists()) {
            return AjaxResult.error();
        }

        List<File> filesAll = FileUtil.loopFiles(file.getAbsolutePath());
        if (filesAll.size() < 2) {
            //分片缺少 删除全部分片文件 ,从新上传
            FileUtil.clean(absolutePath);
            return AjaxResult.error();
        }

        //从小到大文件进行按照序号排序,和判断分片是否损坏
        List<String> collect = null;
        try {
            collect = fileSliceIsbadAndSort(file, fileSliceSize);
        } catch (Exception e) {
            if (e instanceof SliceBadException) {
                FileUtil.clean(absolutePath);
                return AjaxResult.error();
            }
        }
        //获取最后一个分片
        String fileSliceLatest = collect.get(collect.size() - 1);
        int code = fileId(fileSliceLatest);
        //服务器的分片总大小必须小于或者等于文件的总大小
        if ((code * fileSliceSize) <= fileSize) {
            JSONObject obj = JSONUtil.createObj();
            obj.set("code", String.valueOf(code));
            obj.set("fileSliceLatest", fileSliceLatest);
            obj.set("progressNow", collect.size());
            return AjaxResult.success(obj);
        } else {
            //分片异常 ,删除全部分片文件,从新上传
            FileUtil.clean(absolutePath);
            return AjaxResult.error();
        }
    }

    @PostMapping(value = "/uploads")
    @ResponseBody
    public AjaxResult uploads(HttpServletRequest request, @RequestParam("part") MultipartFile part) throws IOException {
        String fileSliceName = request.getParameter("fileSliceName");
        long fileSize = Long.parseLong(request.getParameter("fileSize")); //文件大小
        String dir = fileSliceMd5Dir(fileSliceName, fileSize);
        File file = FileUtil.mkdir(physicalPath + uploadslicedir + dir + File.separator + fileSliceName);
        part.transferTo(file);
        int i = fileId(file.getName());
//        try {
//            //模拟耗时操作，要不前端显示太快,正式时删除
//            Thread.sleep(RandomUtil.randomInt(2000, 5000, true, true));
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        return AjaxResult.success(i);
    }

    // 合并分片
    @GetMapping(value = "/merge-file-slice/{fileSliceName}/{fileSliceSize}/{fileSize}")
    @ResponseBody
    public AjaxResult mergeFileSlice(@PathVariable String fileSliceName, @PathVariable long fileSliceSize, @PathVariable long fileSize) throws Exception {
        int l = (int) Math.ceil((double) fileSize / fileSliceSize); //有多少个分片
        String dir = fileSliceMd5Dir(fileSliceName, fileSize); //分片所在的目录
        String absolutePath = physicalPath + uploadslicedir + dir;
        File file = FileUtil.mkdir(absolutePath);
        String realFileName;
        String realFileNamePath;

        if (file.exists()) {
            List<String> filesAll = FileUtil.listFileNames(absolutePath);


            //阻塞循环判断是否还在上传  ,解决前端进行ajax异步上传的问题
            int beforeSize = filesAll.size();
            while (true) {
                if (filesAll.size() == l) {
                    break;
                }
                Thread.sleep(1000);
                //之前分片数量和现在分片数据之差,如果大于1那么就在上传,那么继续
                filesAll = FileUtil.listFileNames(absolutePath);
                if (filesAll.size() - beforeSize >= 1) {
                    beforeSize = filesAll.size();
                    //继续检测
                    continue;
                }
                //如果是之前分片和现在的分片相等的,那么在阻塞2秒后检测是否发生变化,如果还没变化那么上传全部完成,可以进行合并了
                //当然这不是绝对的,只能解决网络短暂的波动,因为有可能发生断网很长时间,网络恢复后文件恢复上传, 这个问题是避免不了的,所以我们在下面的代码进行数量的效验
                // 因为我们不可能一直等着他网好,所以如果1~3秒内没有上传新的内容,那么我们默认判定上传完毕
                if (beforeSize == filesAll.size()) {
                    Thread.sleep(2000);
                    filesAll = FileUtil.listFileNames(absolutePath);
                    if (beforeSize == filesAll.size()) {
                        break;
                    }
                }
            }
            //分片数量效验
            if (filesAll.size() != l) {
                //分片缺少 ,删除全部分片文件,从新上传
                FileUtil.clean(absolutePath);
                return AjaxResult.error();
            }
            //获取实际的文件名称,组装路径
            realFileName = realFileName(fileSliceName);
            realFileNamePath = physicalPath + uploaddir + realFileName;

            //从小到大文件进行按照序号排序 ,和检查分片文件是否有问题
            List<String> collect = fileSliceIsbadAndSort(file, fileSliceSize);
            int fileSliceCount = collect.size();
            FileUtil.touch(realFileNamePath);

            List<Future<?>> futures = new ArrayList<>();
            for (int i = 0; i < fileSliceCount; i++) {
                int finalI = i;
                Future<?> future = ExecutorUtils.createFuture(() -> {
                    String fileNameTemp = collect.get(finalI);
                    long fileTempSize = new File(absolutePath + File.separator+fileNameTemp).length();
                    byte[] bytes = new byte[(int) fileTempSize];
                    try (RandomAccessFile r = new RandomAccessFile(absolutePath + File.separator +fileNameTemp, "r")) {
                        r.read(bytes, 0, (int) fileTempSize);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                    try (RandomAccessFile w = new RandomAccessFile(realFileNamePath, "rw")) {
                        //当前文件写入的位置
                        w.seek(finalI * fileSliceSize);
                        w.write(bytes);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return 1;
                });
                futures.add(future);
            }
            //阻塞到全部线程执行完毕后
            ExecutorUtils.waitComplete(futures);
            //删除全部分片文件
            FileUtil.clean(absolutePath);
        } else {
            //没有这个分片相关的的目录
            return AjaxResult.error();
        }
        return AjaxResult.success(virtualPath + realFileName);
    }


    //获取分片文件的目录
    private String fileSliceMd5Dir(String fileSliceName, long fileSize) {
        int i = fileSliceName.indexOf(identification);
        String substring = fileSliceName.substring(0, i);
        String dir = DigestUtil.md5Hex(substring + fileSize);
        return dir;
    }

    //通过文件名称获取文件目录
    private String fileNameMd5Dir(String fileName, long fileSize) {
        return DigestUtil.md5Hex(fileName + fileSize);
    }

    //获取分片的实际文件名
    private String realFileName(String fileSliceName) {
        int i = fileSliceName.indexOf(identification);
        String substring = fileSliceName.substring(0, i);
        return substring;

    }

    //获取文件序号
    private int fileId(String fileSliceName) {
        int i = fileSliceName.indexOf(identification) + identification.length();
        String fileId = fileSliceName.substring(i);
        return Integer.parseInt(fileId);
    }

    //判断是否损坏
    private List<String> fileSliceIsbadAndSort(File file, long fileSliceSize) throws Exception {
        String absolutePath = file.getAbsolutePath();
        List<String> filesAll = FileUtil.listFileNames(absolutePath);
        if (filesAll.size() < 1) {
            //分片缺少,删除全部分片文件 ,从新上传
            FileUtil.clean(absolutePath);
            throw new SliceBadException();
        }
        //从小到大文件进行按照序号排序
        List<String> collect = filesAll.stream().sorted((a, b) -> fileId(a) - fileId(b)).collect(Collectors.toList());
        //判断文件是否损坏,将文件排序后,进行前后序号相差大于1那么就代表少分片了
        for (int i = 0; i < collect.size() - 1; i++) {
            //检测分片的连续度
            if (fileId(collect.get(i)) - fileId(collect.get(i + 1)) != -1) {
                //分片损坏 删除全部分片文件 ,从新上传
                FileUtil.clean(absolutePath);
                throw new SliceBadException();
            }
            //检测分片的完整度
            if (new File(absolutePath + File.separator + collect.get(i)).length() != fileSliceSize) {
                //分片损坏 删除全部分片文件 ,从新上传
                FileUtil.clean(absolutePath);
                throw new SliceBadException();
            }
        }
        return collect;
    }


}
